package com.gitee.ixtf.lucene.kotlinx

import com.gitee.ixtf.core.J
import com.gitee.ixtf.core.kotlinx.date
import com.gitee.ixtf.lucene.Jlucene.CREATE_DATE_TIME
import com.gitee.ixtf.lucene.Jlucene.CREATOR
import com.gitee.ixtf.lucene.Jlucene.ID
import com.gitee.ixtf.lucene.Jlucene.MODIFIER
import com.gitee.ixtf.lucene.Jlucene.MODIFY_DATE_TIME
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
import kotlinx.coroutines.flow.*
import org.apache.lucene.analysis.Analyzer
import org.apache.lucene.document.*
import org.apache.lucene.index.Term
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser
import org.apache.lucene.search.*
import org.apache.lucene.search.BooleanClause.Occur
import org.apache.lucene.search.BooleanClause.Occur.MUST
import org.apache.lucene.util.BytesRef

fun BooleanQuery.Builder.query(query: Query?, occur: Occur = MUST) {
  query?.also { add(it, occur) }
}

fun BooleanQuery.Builder.query(builder: BooleanQuery.Builder?, occur: Occur = MUST) {
  query(builder?.build(), occur)
}

fun BooleanQuery.Builder.text(
    analyzer: Analyzer,
    q: String?,
    vararg fields: String,
    occur: Occur = MUST
) =
    query(
        q?.takeIf { it.isNotBlank() }?.let { MultiFieldQueryParser(fields, analyzer).parse(it) },
        occur)

fun BooleanQuery.Builder.id(v: String?, occur: Occur = MUST) = keyword(ID, v, occur)

fun BooleanQuery.Builder.creator(v: String?, occur: Occur = MUST) = keyword(CREATOR, v, occur)

fun BooleanQuery.Builder.modifier(v: String?, occur: Occur = MUST) = keyword(MODIFIER, v, occur)

fun BooleanQuery.Builder.createDateTime(min: Date?, max: Date?, occur: Occur = MUST) =
    range(CREATE_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.createDateTime(min: LocalDate?, max: LocalDate?, occur: Occur = MUST) =
    range(CREATE_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.createDateTime(
    min: LocalDateTime?,
    max: LocalDateTime?,
    occur: Occur = MUST
) = range(CREATE_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.modifyDateTime(min: Date?, max: Date?, occur: Occur = MUST) =
    range(MODIFY_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.modifyDateTime(min: LocalDate?, max: LocalDate?, occur: Occur = MUST) =
    range(MODIFY_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.modifyDateTime(
    min: LocalDateTime?,
    max: LocalDateTime?,
    occur: Occur = MUST
) = range(MODIFY_DATE_TIME, min, max, occur)

fun BooleanQuery.Builder.keyword(field: String, v: String?, occur: Occur = MUST) =
    query(v?.takeIf { it.isNotBlank() }?.let { KeywordField.newExactQuery(field, it) }, occur)

suspend fun BooleanQuery.Builder.keyword(field: String, v: Flow<String?>?, occur: Occur = MUST) =
    query(
        v?.filter { J.nonBlank(it) }
            ?.map { BytesRef(it) }
            ?.toList()
            ?.takeIf { it.isNotEmpty() }
            ?.run {
              if (size == 1) KeywordField.newExactQuery(field, first())
              else KeywordField.newSetQuery(field, this)
            },
        occur)

suspend fun BooleanQuery.Builder.keyword(field: String, v: Iterable<String>?, occur: Occur = MUST) =
    keyword(field, v?.asFlow(), occur)

suspend fun BooleanQuery.Builder.keyword(field: String, vararg v: String?, occur: Occur = MUST) =
    keyword(field, v.asFlow(), occur)

fun BooleanQuery.Builder.enum(field: String, e: Enum<*>?, occur: Occur = MUST) =
    keyword(field, e?.name, occur)

suspend fun BooleanQuery.Builder.enum(field: String, vararg e: Enum<*>?, occur: Occur = MUST) =
    keyword(field, e.asFlow().map { it?.name }, occur)

suspend fun BooleanQuery.Builder.enum(field: String, e: Iterable<Enum<*>>?, occur: Occur = MUST) =
    keyword(field, e?.asFlow()?.map { it.name }, occur)

fun BooleanQuery.Builder.boolean(field: String, b: Boolean?, occur: Occur = MUST) {
  b?.let { add(IntPoint.newExactQuery(field, if (b) 1 else 0), occur) }
}

fun BooleanQuery.Builder.range(field: String, min: Date?, max: Date?, occur: Occur = MUST) =
    range(field, min?.time, max?.time, occur)

fun BooleanQuery.Builder.range(
    field: String,
    min: LocalDate?,
    max: LocalDate?,
    occur: Occur = MUST
) = range(field, min?.date(), max?.date(), occur)

fun BooleanQuery.Builder.range(
    field: String,
    min: LocalDateTime?,
    max: LocalDateTime?,
    occur: Occur = MUST
) = range(field, min?.date(), max?.date(), occur)

fun BooleanQuery.Builder.range(field: String, min: Int?, max: Int?, occur: Occur = MUST) {
  if (min != null && max != null) {
    add(IntPoint.newRangeQuery(field, min, max), occur)
  } else if (min != null) {
    add(IntPoint.newRangeQuery(field, min, Int.MAX_VALUE), occur)
  } else if (max != null) {
    add(IntPoint.newRangeQuery(field, Int.MIN_VALUE, max), occur)
  }
}

fun BooleanQuery.Builder.range(field: String, min: Long?, max: Long?, occur: Occur = MUST) {
  if (min != null && max != null) {
    add(LongPoint.newRangeQuery(field, min, max), occur)
  } else if (min != null) {
    add(LongPoint.newRangeQuery(field, min, Long.MAX_VALUE), occur)
  } else if (max != null) {
    add(LongPoint.newRangeQuery(field, Long.MIN_VALUE, max), occur)
  }
}

fun BooleanQuery.Builder.range(field: String, min: Double?, max: Double?, occur: Occur = MUST) {
  if (min != null && max != null) {
    add(DoublePoint.newRangeQuery(field, min, max), occur)
  } else if (min != null) {
    add(DoublePoint.newRangeQuery(field, min, Double.MAX_VALUE), occur)
  } else if (max != null) {
    add(DoublePoint.newRangeQuery(field, Double.MIN_VALUE, max), occur)
  }
}

fun BooleanQuery.Builder.wildcard(field: String, q: String?, occur: Occur = MUST) {
  q?.takeIf { it.isNotBlank() }?.let { WildcardQuery(Term(field, it)) }.also { add(it, occur) }
}

fun BooleanQuery.Builder.string(field: String, v: String?, occur: Occur = MUST) {
  v?.takeIf { it.isNotBlank() }?.let { TermQuery(Term(field, it)) }?.also { add(it, occur) }
}

fun BooleanQuery.Builder.string(field: String, v: Sequence<String>?, occur: Occur = MUST) {
  v?.filter { it.isNotBlank() }
      ?.map { BytesRef(it) }
      ?.toList()
      ?.takeIf { it.isNotEmpty() }
      ?.run { if (size == 1) TermQuery(Term(field, first())) else TermInSetQuery(field, this) }
      ?.also { add(it, occur) }
}

fun BooleanQuery.Builder.string(field: String, v: Iterable<String>?, occur: Occur = MUST) =
    string(field, v?.asSequence(), occur)

fun BooleanQuery.Builder.string(field: String, vararg v: String?, occur: Occur = MUST) =
    string(field, v.asSequence().filterNotNull(), occur)
